package com.baby.core;

import java.lang.reflect.ParameterizedType;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.ibatis.builder.xml.dynamic.ForEachSqlNode;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * mybatis通用DAO
 * 
 * @author xiecs
 * 
 * @param <T>
 */
@SuppressWarnings(value = { "unchecked" })
public class GenericDao<T> {

	public static final String SELECTBYPK = ".selectByPK";
	public static final String INSERT = ".insert";
	public static final String BATCHINSERT = ".batchInsert";
	public static final String INSERTSELECTIVE = ".insertSelective";
	public static final String DELETEBYPK = ".deleteByPK";
	public static final String UPDATEBYPK = ".updateByPK";
	public static final String UPDATESELECTIVEBYPK = ".updateSelectiveByPK";
	public static final String SELECTBYARGS = ".selectByArgs";
	public static final String SELECTALLNOTARGS = ".selectAllNotArgs";
	public static final String SELECTLISTBYARGS = ".selectListByArgs";

	// public static final String SELECTBYIDS = ".selectByIds";
	// public static final String SELECTBYMAP = ".selectByMap";
	// public static final String SELECTIDSLIKEBYMAP = ".selectIdsLikeByMap";
	// public static final String PKSELECTMAP = ".pkSelectByMap";
	// public static final String COUNT = ".count";
	// public static final String COUNTLIKEBYMAP = ".countLikeByMap";
	// public static final String DELETEBYIDS = ".deleteByIds";
	// public static final String DELETEBYIDSMAP = ".deleteByIdsMap";
	// public static final String DELETEBYMAP = ".deleteByMap";
	// public static final String UPDATEBYMAP = ".updateByMap";
	// public static final String UPDATEBYIDSMAP = ".updateByIdsMap";

	protected Class<T> clazz;
	protected String clazzName;

	@Autowired
	protected SqlSessionTemplate sqlSessionTemplate;

	@Autowired
	protected SqlSessionFactory sqlSessionFactory;

	protected T t;

	public GenericDao() {
		// 通过范型反射，取得在子类中定义的class.
		clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
		clazzName = clazz.getSimpleName();
	}

	/**
	 * 获取数据库时间
	 * 
	 * @return
	 */
	public Date getSysDate() {
		return (Date) sqlSessionTemplate.selectOne("Common.getSysDate");
	}

	/**
	 * 获取Sequence
	 * 
	 * @param name
	 * @return
	 */
	public Long getSequence(String name) {
		HashMap<String, String> m = new HashMap<String, String>();
		m.put("name", name);
		return (Long) sqlSessionTemplate.selectOne("Common.getSequence", m);
	}

	/**
	 * 通过主键得到单个实体对象
	 */
	public T selectByPK(Object id) {
		return (T) sqlSessionTemplate.selectOne(clazzName + SELECTBYPK, id);
	}

	/**
	 * 通过查询条件得到单个实体对象
	 */
	public T selectByArgs(HashMap<String, Object> args) {
		return (T) sqlSessionTemplate.selectOne(clazzName + SELECTBYARGS, args);
	}

	/**
	 * 通过查询条件得到实体对象列表
	 */
	public List<T> selectListByArgs(HashMap<String, Object> args) {
		return sqlSessionTemplate.selectList(clazzName + SELECTLISTBYARGS, args);
	}

	/**
	 * 获取所有对象列表，无查询参数
	 * 
	 * @return
	 */
	public List<T> selectAllNotArgs() {
		return sqlSessionTemplate.selectList(clazzName + SELECTALLNOTARGS);
	}

	/**
	 * 
	 * @param obj
	 * @return 更新成功的行数
	 */
	public int updateByPK(T obj) {
		return sqlSessionTemplate.update(clazzName + UPDATEBYPK, obj);
	}

	/**
	 * 
	 * @param obj
	 * @return 更新成功的行数
	 */
	public int updateSelectiveByPK(T obj) {
		return sqlSessionTemplate.update(clazzName + UPDATESELECTIVEBYPK, obj);
	}

	/**
	 * obj中的属性值不可为空
	 * 
	 * @param obj
	 */
	public int insert(T obj) {
		return sqlSessionTemplate.insert(clazzName + INSERT, obj);
	}

	/**
	 * 根据obj是否为null生成sql
	 * 
	 * @param obj
	 */
	public int insertSelective(T obj) {
		return sqlSessionTemplate.insert(clazzName + INSERTSELECTIVE, obj);
	}

	/**
	 * 批量插入，如果语句中包含select，仍然会打开多个conn
	 * 
	 * @param list
	 * @return
	 */
	public void batchInsert(List<T> list) {
		SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
		for (T obj : list) {
			sqlSession.insert(clazzName + INSERT, obj);
		}
		sqlSession.commit();
		sqlSession.close();
	}

	/**
	 * 根据主键删除记录
	 * 
	 * @param obj
	 * @return
	 */
	public int deleteByPK(T obj) {
		return sqlSessionTemplate.delete(clazzName + DELETEBYPK, obj);
	}

	/**
	 * 条件查询
	 * 
	 * @param 查询条件
	 * @return 对象列表
	 */
	List<T> queryByArgs(Object queryObj) {
		if (queryObj == null)
			return sqlSessionTemplate.selectList(clazzName + SELECTBYARGS);
		else
			return sqlSessionTemplate.selectList(clazzName + SELECTBYARGS, queryObj);
	}

	/**
	 * 内置分页查询，需要xml配合分页sql
	 * 
	 * @param page
	 * @return
	 */
	public Result<T> pageByArgs(Page page) {
		Long count = (Long) sqlSessionTemplate.selectOne(clazzName + ".pageCountByArgs", page);
		List<T> list = sqlSessionTemplate.selectList(clazzName + ".pageByArgs", page);

		Result<T> result = new Result<T>();
		result.setContent(list);
		page.setTotalItems(count);
		result.setPage(page);
		return result;
	}

	/**
	 * 自定义分页查询，不用写分页语言，基于mybatis插件
	 * 
	 * @param page
	 * @param pageCountSql
	 *            查询总数sql
	 * @param pageSql
	 *            分页查询sql
	 * @return
	 */
	public Result<T> pageByArgsSelective(Page page, String pageSql) {
		// Long count = (Long) sqlSessionTemplate.selectOne(clazzName + "." +
		// pageCountSql, page);
		Long count = getTotalCount(sqlSessionTemplate, clazzName + "." + pageSql, page);

		List<T> list = sqlSessionTemplate.selectList(clazzName + "." + pageSql, page,
				new RowBounds(page.getBeginIndex(), page.getEndIndex()));
		Result<T> result = new Result<T>();
		result.setContent(list);
		page.setTotalItems(count);
		result.setPage(page);
		return result;
	}

	private long getTotalCount(SqlSessionTemplate sqlSession, String statementName, Object values) {
		Map parameterMap = toParameterMap(values);
		long count = 0l;

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		Connection conn = null;
		try {
			MappedStatement mst = sqlSession.getConfiguration().getMappedStatement(statementName);
			BoundSql boundSql = mst.getBoundSql(parameterMap);
			String sql = " select count(*) total_count from (" + boundSql.getSql() + ") ";
			conn = sqlSession.getSqlSessionFactory().openSession().getConnection();
			pstmt = conn.prepareStatement(sql);
			// BoundSql countBS = new
			// BoundSql(mst.getConfiguration(),sql,boundSql.getParameterMappings(),parameterMap);
			setParameters(pstmt, mst, boundSql, parameterMap);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				count = rs.getLong("total_count");
			}
		} catch (Exception e) {
			count = 0l;
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			try {
				if (rs != null)
					rs.close();
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return count;
	}

	/**
	 * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.
	 * DefaultParameterHandler
	 * 
	 * @param ps
	 * @param mappedStatement
	 * @param boundSql
	 * @param parameterObject
	 * @throws SQLException
	 */
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
							&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value).getValue(
									propertyName.substring(prop.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject.getValue(propertyName);
					}
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName
								+ " of statement " + mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
				}
			}
		}
	}

	protected Map toParameterMap(Object parameter) {
		if (parameter == null) {
			return new HashMap();
		}

		if (parameter instanceof Map) {
			return (Map<?, ?>) parameter;
		} else {
			try {
				return PropertyUtils.describe(parameter);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
	}

	// public Long pageCountByArgs(Page page) {
	// return (Long) sqlMapClientTemplate.queryForObject(
	// ".pageCountByArgs", page);
	// }

}
